Executive Summary: Response Density Analysis

🎯 Business Problem

Density Analysis: Measure whether survey responses are concentrated among a small number of heavy users or distributed evenly across many panelists. This helps understand user engagement patterns and potential bias in survey results.

📊 Final Results

Task 2.1 Answer: 15.68% of all users account for 50% of survey responses

Task 2.2 Answer: 28.66% of recent users (90 days) account for 50% of their responses

Task 2.3 Answer: Lorenz curve visualization showing concentration at all thresholds


📋 Data Processing Workflow

Step 1: Data Cleaning Strategy

  • Remove missing IDs: Can’t attribute responses without user identifiers
  • Standardize formats: Ensure consistent ID types for proper joins
  • Parse dates: Enable accurate 90-day filtering

Step 2: User Response Counting

  • Aggregate: Count total responses per user ID
  • Rank: Sort users by response count (descending)
  • Calculate: Running totals and percentages

Step 3: Cumulative Analysis

  • Running sum: Cumulative responses from top users
  • Percentage calculation: Convert to proportion of total responses
  • Threshold detection: Find where cumulative share crosses target

Step 4: Lorenz Curve Construction

  • X-axis: Cumulative percentage of users (0% to 100%)
  • Y-axis: Cumulative percentage of responses (0% to 100%)
  • Interpretation: Closer to diagonal = more equal distribution

Task 2.1: All Users Density Analysis

Summary

Calculate minimum percentage of ALL Verasight users accounting for 50% of survey responses.

Workflow

  1. Join datasets: Link response database with user information
  2. Count responses: Aggregate responses per user ID
  3. Sort & rank: Order users by response count (highest first)
  4. Calculate cumulative: Running totals of responses and user percentages
  5. Find threshold: Identify where cumulative responses ≥ 50%

Answer: 15.68% of users account for 50% of all survey responses


Task 2.2: Recent Users (90 Days) Density Analysis

Summary

Calculate same density metric but restricted to users who registered within last 90 days.

Workflow

  1. Define 90-day window: Calculate cutoff date from most recent signup
  2. Filter recent users: Subset to users registered within window
  3. Subset responses: Only include responses from recent users
  4. Recalculate metrics: Apply same cumulative analysis to recent cohort
  5. Compare results: Analyze difference vs all-time users

Answer: 28.66% of recent users account for 50% of responses from that cohort


Task 2.3: Visualization - Lorenz Curve Analysis

Summary

Create detailed visualization showing density across multiple thresholds (10%, 20%, 30%, etc.).

Workflow

  1. Generate thresholds: Calculate user percentages for 10%-90% response shares
  2. Create Lorenz curve: Plot cumulative users vs cumulative responses
  3. Add reference line: Perfect equality diagonal for comparison
  4. Interpret results: Distance from diagonal shows concentration level

Answer: Interactive Lorenz curve showing power-law distribution pattern

Key Insights: - High concentration: Small elite drives majority of responses - Power-law pattern: Typical of user engagement platforms - Inequality measure: Distance from diagonal indicates response concentration


📈 Business Insights & Conclusions

Key Findings Summary

Concentration Patterns:

  1. All Users: 15.68% account for 50% of responses → High concentration
  2. Recent Users: 28.66% account for 50% of responses → Even higher concentration
  3. Early Threshold: Only 2.07% of users account for 10% of responses

Strategic Implications:

  • Survey bias risk: Small user subset drives majority of feedback
  • Engagement opportunity: Large portion of users underutilized
  • Quality focus: Heavy users require special attention for data quality
  • Growth potential: Recent users show higher engagement rates

Technical Validation:

  • Formula accuracy: min(which()) correctly identifies threshold crossings
  • Data integrity: Proper handling of missing IDs ensures valid user attribution
  • Visualization clarity: Lorenz curve effectively shows concentration patterns

File Outputs Generated

Output Structure:

task2/outputs/
├── all_users_analysis.csv         # Detailed user-level analysis
├── recent_users_analysis.csv      # 90-day cohort analysis  
├── density_thresholds_summary.csv # Multiple threshold results
├── all_vs_recent_comparison.csv   # Comparative analysis
├── lorenz_curve_all_users.png     # Lorenz curve visualization
└── user_comparison.png            # All vs recent comparison plot

💻 Code Implementation

Below is the complete technical implementation of the analysis described above.

cat("=== DATA CLEANING & PREPARATION ===\n")
=== DATA CLEANING & PREPARATION ===
# Check missing values
cat("Missing IDs in response database:", sum(is.na(full_db$ID)), "rows\n")
Missing IDs in response database: 43692 rows
cat("These must be removed because we cannot attribute responses to users without IDs\n\n")
These must be removed because we cannot attribute responses to users without IDs
# Remove rows with missing user ID (cannot be attributed to any user)
full_db_clean <- full_db %>%
  filter(!is.na(ID))

cat("After cleaning - usable responses:", nrow(full_db_clean), "\n")
After cleaning - usable responses: 144111 
cat("=== TASK 2.1: ALL USERS ANALYSIS ===\n")
=== TASK 2.1: ALL USERS ANALYSIS ===
# Step 1: Count responses per user
user_counts <- full_db_clean %>%
  count(ID, name = "response_count")

# Step 2: Join with user data and calculate cumulative statistics
user_data <- user_counts %>%
  left_join(users, by = "ID") %>%
  arrange(desc(response_count)) %>% # Sort by highest responders first
  mutate(
    # Calculate running totals
    cumulative_responses = cumsum(response_count),
    total_responses = sum(response_count),
    cumulative_pct = cumulative_responses / total_responses,
    user_rank = row_number(),
    user_pct = user_rank / n() # Percentage of users represented
  )

# Step 3: Find minimum users for 50% threshold using min(which())
cutoff_row <- min(which(user_data$cumulative_pct >= 0.5))
pct_50_all <- user_data$user_pct[cutoff_row] * 100

cat(sprintf("ANSWER: %.2f%% of users account for 50%% of all responses\n", pct_50_all))
ANSWER: 15.68% of users account for 50% of all responses
# Save detailed analysis
write_csv(user_data, file.path(output_dir, "all_users_analysis.csv"))
cat("=== TASK 2.2: RECENT USERS (90 DAYS) ===\n")
=== TASK 2.2: RECENT USERS (90 DAYS) ===
# Step 1: Parse signup dates and define 90-day window
users$signup_date <- as.Date(users$signup_date, format = "%m/%d/%Y")
cutoff_date <- max(users$signup_date, na.rm = TRUE) - 90
cat("90-day cutoff date:", as.character(cutoff_date), "\n")
90-day cutoff date: 2024-06-18 
# Step 2: Filter to recent users only
recent_users <- users %>%
  filter(signup_date >= cutoff_date)
cat("Recent users found:", nrow(recent_users), "\n")
Recent users found: 18019 
# Step 3: Subset response data for recent users
recent_data <- user_data %>%
  filter(ID %in% recent_users$ID) %>%
  arrange(desc(response_count)) %>%
  mutate(
    # Recalculate cumulative stats for recent cohort only
    cumulative_responses = cumsum(response_count),
    total_responses = sum(response_count),
    cumulative_pct = cumulative_responses / total_responses,
    user_rank = row_number(),
    user_pct = user_rank / n()
  )

# Step 4: Find 50% threshold for recent users
cutoff_row_recent <- min(which(recent_data$cumulative_pct >= 0.5))
pct_50_recent <- recent_data$user_pct[cutoff_row_recent] * 100

cat(sprintf("ANSWER: %.2f%% of recent users account for 50%% of their responses\n", pct_50_recent))
ANSWER: 28.66% of recent users account for 50% of their responses
# Save recent users analysis
write_csv(recent_data, file.path(output_dir, "recent_users_analysis.csv"))

# Comparison insight
cat("\nCOMPARISON:\n")

COMPARISON:
cat("All users:", round(pct_50_all, 2), "%\n")
All users: 15.68 %
cat("Recent users:", round(pct_50_recent, 2), "%\n")
Recent users: 28.66 %
if(pct_50_recent < pct_50_all) {
  cat("Recent users show HIGHER concentration (more active heavy users)\n")
} else {
  cat("Recent users show LOWER concentration (more distributed engagement)\n")
}
Recent users show LOWER concentration (more distributed engagement)
cat("=== TASK 2.3: MULTIPLE THRESHOLD ANALYSIS ===\n")
=== TASK 2.3: MULTIPLE THRESHOLD ANALYSIS ===
# Calculate user percentages needed for various response thresholds
response_thresholds <- c(0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90)

density_summary <- sapply(response_thresholds, function(thresh) {
  cutoff_index <- min(which(user_data$cumulative_pct >= thresh))
  user_data$user_pct[cutoff_index] * 100
})

names(density_summary) <- paste0(response_thresholds * 100, "%")

cat("User percentage needed for each response threshold:\n")
User percentage needed for each response threshold:
for (i in 1:length(density_summary)) {
  cat(sprintf("• %.2f%% of users → %s of responses\n", 
              density_summary[i], names(density_summary)[i]))
}
• 2.07% of users → 10% of responses
• 4.68% of users → 20% of responses
• 7.79% of users → 30% of responses
• 11.43% of users → 40% of responses
• 15.68% of users → 50% of responses
• 20.99% of users → 60% of responses
• 28.01% of users → 70% of responses
• 38.31% of users → 80% of responses
• 55.92% of users → 90% of responses
# Create summary table
summary_table <- data.frame(
  Response_Threshold = names(density_summary),
  User_Percentage_Needed = round(density_summary, 2)
)

write_csv(summary_table, file.path(output_dir, "density_thresholds_summary.csv"))
cat("=== LORENZ CURVE VISUALIZATION ===\n")
=== LORENZ CURVE VISUALIZATION ===
# Create Lorenz curve plot
lorenz_plot <- ggplot(user_data, aes(x = user_pct, y = cumulative_pct)) +
  geom_line(color = "darkblue", size = 1.2, alpha = 0.8) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", 
              color = "gray50", alpha = 0.7) +
  annotate("text", x = 0.7, y = 0.3, 
           label = "Perfect Equality\n(45° line)", 
           color = "gray50", size = 3) +
  annotate("text", x = 0.3, y = 0.7, 
           label = "Actual Distribution\n(High Concentration)", 
           color = "darkblue", size = 3) +
  scale_x_continuous(labels = scales::percent, name = "Cumulative % of Users") +
  scale_y_continuous(labels = scales::percent, name = "Cumulative % of Responses") +
  labs(title = "Response Density: Lorenz Curve Analysis",
       subtitle = paste0("Distance from diagonal shows concentration level | ",
                        round(pct_50_all, 1), "% of users → 50% of responses")) +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(size = 14, face = "bold"))
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
Please use `linewidth` instead.
# Display interactive version
interactive_lorenz <- ggplotly(lorenz_plot, tooltip = c("x", "y"))
interactive_lorenz

# Save static version
ggsave(file.path(output_dir, "lorenz_curve_all_users.png"), lorenz_plot, 
       width = 10, height = 6, dpi = 300)

cat("Lorenz Curve Interpretation:\n")
Lorenz Curve Interpretation:
cat("• Diagonal line = Perfect equality (everyone contributes equally)\n")
• Diagonal line = Perfect equality (everyone contributes equally)
cat("• Curved line = Actual distribution (concentrated among few users)\n")
• Curved line = Actual distribution (concentrated among few users)
cat("• Larger area between lines = Higher concentration\n")
• Larger area between lines = Higher concentration
cat("=== COMPARISON: ALL vs RECENT USERS ===\n")
=== COMPARISON: ALL vs RECENT USERS ===
# Calculate thresholds for recent users
recent_summary <- sapply(response_thresholds, function(thresh) {
  if (max(recent_data$cumulative_pct) >= thresh) {
    cutoff_index <- min(which(recent_data$cumulative_pct >= thresh))
    recent_data$user_pct[cutoff_index] * 100
  } else {
    NA
  }
})

# Create comparison dataset
comparison_data <- data.frame(
  Response_Threshold = response_thresholds * 100,
  All_Users = density_summary,
  Recent_Users = recent_summary
) %>%
  pivot_longer(cols = c(All_Users, Recent_Users), 
               names_to = "User_Group", 
               values_to = "User_Percentage")

# Create comparison plot
comparison_plot <- ggplot(comparison_data, aes(x = Response_Threshold, y = User_Percentage, 
                                              color = User_Group, linetype = User_Group)) +
  geom_line(size = 1.2) +
  geom_point(size = 3) +
  scale_x_continuous(name = "Response Threshold (%)", breaks = seq(10, 90, 10)) +
  scale_y_continuous(name = "User Percentage Needed (%)") +
  scale_color_manual(values = c("All_Users" = "darkblue", "Recent_Users" = "darkred")) +
  labs(title = "Response Concentration: All Users vs Recent Users (90 Days)",
       subtitle = "Lower values indicate higher concentration among fewer users",
       color = "User Group", linetype = "User Group") +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(size = 14, face = "bold"),
        legend.position = "bottom")

print(comparison_plot)
ggsave(file.path(output_dir, "user_comparison.png"), comparison_plot, 
       width = 10, height = 6, dpi = 300)


# Save comparison data
write_csv(comparison_data, file.path(output_dir, "all_vs_recent_comparison.csv"))
cat("=== ANALYSIS COMPLETE ===\n")
=== ANALYSIS COMPLETE ===
cat("Generated files in", normalizePath(output_dir), ":\n")
Generated files in /Users/jacksonzhao/Desktop/ds_case_study_jackson_verasight/tasks/task2/outputs :
output_files <- list.files(output_dir)
for (file in output_files) {
  cat("•", file, "\n")
}
• all_users_analysis.csv 
• all_vs_recent_comparison.csv 
• density_thresholds_summary.csv 
• lorenz_curve_all_users.png 
• recent_users_analysis.csv 
• user_comparison.png 
cat("\nFinal Answers:\n")

Final Answers:
cat("Task 2.1:", round(pct_50_all, 2), "% of all users → 50% of responses\n")
Task 2.1: 15.68 % of all users → 50% of responses
cat("Task 2.2:", round(pct_50_recent, 2), "% of recent users → 50% of their responses\n")
Task 2.2: 28.66 % of recent users → 50% of their responses
cat("Task 2.3: Lorenz curve visualization with multiple threshold analysis\n")
Task 2.3: Lorenz curve visualization with multiple threshold analysis
LS0tCnRpdGxlOiAiVmVyYXNpZ2h0IERhdGEgU2NpZW50aXN0IENhc2UgU3R1ZHkg4oCTIFRhc2sgMjogUmVzcG9uc2UgRGVuc2l0eSBBbmFseXNpcyIKYXV0aG9yOiAiSmFja3NvbiBaaGFvIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIEV4ZWN1dGl2ZSBTdW1tYXJ5OiBSZXNwb25zZSBEZW5zaXR5IEFuYWx5c2lzCgojIyDwn46vIEJ1c2luZXNzIFByb2JsZW0KKipEZW5zaXR5IEFuYWx5c2lzOioqIE1lYXN1cmUgd2hldGhlciBzdXJ2ZXkgcmVzcG9uc2VzIGFyZSBjb25jZW50cmF0ZWQgYW1vbmcgYSBzbWFsbCBudW1iZXIgb2YgaGVhdnkgdXNlcnMgb3IgZGlzdHJpYnV0ZWQgZXZlbmx5IGFjcm9zcyBtYW55IHBhbmVsaXN0cy4gVGhpcyBoZWxwcyB1bmRlcnN0YW5kIHVzZXIgZW5nYWdlbWVudCBwYXR0ZXJucyBhbmQgcG90ZW50aWFsIGJpYXMgaW4gc3VydmV5IHJlc3VsdHMuCgojIyDwn5OKIEZpbmFsIFJlc3VsdHMKCiMjIyAqKlRhc2sgMi4xIEFuc3dlcjoqKiAxNS42OCUgb2YgYWxsIHVzZXJzIGFjY291bnQgZm9yIDUwJSBvZiBzdXJ2ZXkgcmVzcG9uc2VzCiMjIyAqKlRhc2sgMi4yIEFuc3dlcjoqKiAyOC42NiUgb2YgcmVjZW50IHVzZXJzICg5MCBkYXlzKSBhY2NvdW50IGZvciA1MCUgb2YgdGhlaXIgcmVzcG9uc2VzICAKIyMjICoqVGFzayAyLjMgQW5zd2VyOioqIExvcmVueiBjdXJ2ZSB2aXN1YWxpemF0aW9uIHNob3dpbmcgY29uY2VudHJhdGlvbiBhdCBhbGwgdGhyZXNob2xkcwoKLS0tCgoKIyMg8J+TiyBEYXRhIFByb2Nlc3NpbmcgV29ya2Zsb3cKCiMjIyAqKlN0ZXAgMTogRGF0YSBDbGVhbmluZyBTdHJhdGVneSoqCi0gKipSZW1vdmUgbWlzc2luZyBJRHM6KiogQ2FuJ3QgYXR0cmlidXRlIHJlc3BvbnNlcyB3aXRob3V0IHVzZXIgaWRlbnRpZmllcnMKLSAqKlN0YW5kYXJkaXplIGZvcm1hdHM6KiogRW5zdXJlIGNvbnNpc3RlbnQgSUQgdHlwZXMgZm9yIHByb3BlciBqb2lucwotICoqUGFyc2UgZGF0ZXM6KiogRW5hYmxlIGFjY3VyYXRlIDkwLWRheSBmaWx0ZXJpbmcKCiMjIyAqKlN0ZXAgMjogVXNlciBSZXNwb25zZSBDb3VudGluZyoqCi0gKipBZ2dyZWdhdGU6KiogQ291bnQgdG90YWwgcmVzcG9uc2VzIHBlciB1c2VyIElECi0gKipSYW5rOioqIFNvcnQgdXNlcnMgYnkgcmVzcG9uc2UgY291bnQgKGRlc2NlbmRpbmcpCi0gKipDYWxjdWxhdGU6KiogUnVubmluZyB0b3RhbHMgYW5kIHBlcmNlbnRhZ2VzCgojIyMgKipTdGVwIDM6IEN1bXVsYXRpdmUgQW5hbHlzaXMqKgotICoqUnVubmluZyBzdW06KiogQ3VtdWxhdGl2ZSByZXNwb25zZXMgZnJvbSB0b3AgdXNlcnMKLSAqKlBlcmNlbnRhZ2UgY2FsY3VsYXRpb246KiogQ29udmVydCB0byBwcm9wb3J0aW9uIG9mIHRvdGFsIHJlc3BvbnNlcwotICoqVGhyZXNob2xkIGRldGVjdGlvbjoqKiBGaW5kIHdoZXJlIGN1bXVsYXRpdmUgc2hhcmUgY3Jvc3NlcyB0YXJnZXQKCiMjIyAqKlN0ZXAgNDogTG9yZW56IEN1cnZlIENvbnN0cnVjdGlvbioqCi0gKipYLWF4aXM6KiogQ3VtdWxhdGl2ZSBwZXJjZW50YWdlIG9mIHVzZXJzICgwJSB0byAxMDAlKQotICoqWS1heGlzOioqIEN1bXVsYXRpdmUgcGVyY2VudGFnZSBvZiByZXNwb25zZXMgKDAlIHRvIDEwMCUpCi0gKipJbnRlcnByZXRhdGlvbjoqKiBDbG9zZXIgdG8gZGlhZ29uYWwgPSBtb3JlIGVxdWFsIGRpc3RyaWJ1dGlvbgoKLS0tCgojIFRhc2sgMi4xOiBBbGwgVXNlcnMgRGVuc2l0eSBBbmFseXNpcwoKIyMgU3VtbWFyeQpDYWxjdWxhdGUgbWluaW11bSBwZXJjZW50YWdlIG9mIEFMTCBWZXJhc2lnaHQgdXNlcnMgYWNjb3VudGluZyBmb3IgNTAlIG9mIHN1cnZleSByZXNwb25zZXMuCgojIyBXb3JrZmxvdwoxLiAqKkpvaW4gZGF0YXNldHM6KiogTGluayByZXNwb25zZSBkYXRhYmFzZSB3aXRoIHVzZXIgaW5mb3JtYXRpb24KMi4gKipDb3VudCByZXNwb25zZXM6KiogQWdncmVnYXRlIHJlc3BvbnNlcyBwZXIgdXNlciBJRAozLiAqKlNvcnQgJiByYW5rOioqIE9yZGVyIHVzZXJzIGJ5IHJlc3BvbnNlIGNvdW50IChoaWdoZXN0IGZpcnN0KQo0LiAqKkNhbGN1bGF0ZSBjdW11bGF0aXZlOioqIFJ1bm5pbmcgdG90YWxzIG9mIHJlc3BvbnNlcyBhbmQgdXNlciBwZXJjZW50YWdlcwo1LiAqKkZpbmQgdGhyZXNob2xkOioqIElkZW50aWZ5IHdoZXJlIGN1bXVsYXRpdmUgcmVzcG9uc2VzIOKJpSA1MCUKCiMjIEFuc3dlcjogKioxNS42OCUgb2YgdXNlcnMgYWNjb3VudCBmb3IgNTAlIG9mIGFsbCBzdXJ2ZXkgcmVzcG9uc2VzKioKCi0tLQoKIyBUYXNrIDIuMjogUmVjZW50IFVzZXJzICg5MCBEYXlzKSBEZW5zaXR5IEFuYWx5c2lzCgojIyBTdW1tYXJ5CkNhbGN1bGF0ZSBzYW1lIGRlbnNpdHkgbWV0cmljIGJ1dCByZXN0cmljdGVkIHRvIHVzZXJzIHdobyByZWdpc3RlcmVkIHdpdGhpbiBsYXN0IDkwIGRheXMuCgojIyBXb3JrZmxvdwoxLiAqKkRlZmluZSA5MC1kYXkgd2luZG93OioqIENhbGN1bGF0ZSBjdXRvZmYgZGF0ZSBmcm9tIG1vc3QgcmVjZW50IHNpZ251cAoyLiAqKkZpbHRlciByZWNlbnQgdXNlcnM6KiogU3Vic2V0IHRvIHVzZXJzIHJlZ2lzdGVyZWQgd2l0aGluIHdpbmRvdwozLiAqKlN1YnNldCByZXNwb25zZXM6KiogT25seSBpbmNsdWRlIHJlc3BvbnNlcyBmcm9tIHJlY2VudCB1c2Vycwo0LiAqKlJlY2FsY3VsYXRlIG1ldHJpY3M6KiogQXBwbHkgc2FtZSBjdW11bGF0aXZlIGFuYWx5c2lzIHRvIHJlY2VudCBjb2hvcnQKNS4gKipDb21wYXJlIHJlc3VsdHM6KiogQW5hbHl6ZSBkaWZmZXJlbmNlIHZzIGFsbC10aW1lIHVzZXJzCgojIyBBbnN3ZXI6ICoqMjguNjYlIG9mIHJlY2VudCB1c2VycyBhY2NvdW50IGZvciA1MCUgb2YgcmVzcG9uc2VzIGZyb20gdGhhdCBjb2hvcnQqKgoKLS0tCgojIFRhc2sgMi4zOiBWaXN1YWxpemF0aW9uIC0gTG9yZW56IEN1cnZlIEFuYWx5c2lzCgojIyBTdW1tYXJ5CkNyZWF0ZSBkZXRhaWxlZCB2aXN1YWxpemF0aW9uIHNob3dpbmcgZGVuc2l0eSBhY3Jvc3MgbXVsdGlwbGUgdGhyZXNob2xkcyAoMTAlLCAyMCUsIDMwJSwgZXRjLikuCgojIyBXb3JrZmxvdwoxLiAqKkdlbmVyYXRlIHRocmVzaG9sZHM6KiogQ2FsY3VsYXRlIHVzZXIgcGVyY2VudGFnZXMgZm9yIDEwJS05MCUgcmVzcG9uc2Ugc2hhcmVzCjIuICoqQ3JlYXRlIExvcmVueiBjdXJ2ZToqKiBQbG90IGN1bXVsYXRpdmUgdXNlcnMgdnMgY3VtdWxhdGl2ZSByZXNwb25zZXMKMy4gKipBZGQgcmVmZXJlbmNlIGxpbmU6KiogUGVyZmVjdCBlcXVhbGl0eSBkaWFnb25hbCBmb3IgY29tcGFyaXNvbgo0LiAqKkludGVycHJldCByZXN1bHRzOioqIERpc3RhbmNlIGZyb20gZGlhZ29uYWwgc2hvd3MgY29uY2VudHJhdGlvbiBsZXZlbAoKIyMgQW5zd2VyOiAqKkludGVyYWN0aXZlIExvcmVueiBjdXJ2ZSBzaG93aW5nIHBvd2VyLWxhdyBkaXN0cmlidXRpb24gcGF0dGVybioqCgoqKktleSBJbnNpZ2h0czoqKgotICoqSGlnaCBjb25jZW50cmF0aW9uOioqIFNtYWxsIGVsaXRlIGRyaXZlcyBtYWpvcml0eSBvZiByZXNwb25zZXMKLSAqKlBvd2VyLWxhdyBwYXR0ZXJuOioqIFR5cGljYWwgb2YgdXNlciBlbmdhZ2VtZW50IHBsYXRmb3JtcwotICoqSW5lcXVhbGl0eSBtZWFzdXJlOioqIERpc3RhbmNlIGZyb20gZGlhZ29uYWwgaW5kaWNhdGVzIHJlc3BvbnNlIGNvbmNlbnRyYXRpb24KCi0tLQoKIyDwn5OIIEJ1c2luZXNzIEluc2lnaHRzICYgQ29uY2x1c2lvbnMKCiMjIEtleSBGaW5kaW5ncyBTdW1tYXJ5CgojIyMgKipDb25jZW50cmF0aW9uIFBhdHRlcm5zOioqCjEuICoqQWxsIFVzZXJzOioqIDE1LjY4JSBhY2NvdW50IGZvciA1MCUgb2YgcmVzcG9uc2VzIOKGkiBIaWdoIGNvbmNlbnRyYXRpb24KMi4gKipSZWNlbnQgVXNlcnM6KiogMjguNjYlIGFjY291bnQgZm9yIDUwJSBvZiByZXNwb25zZXMg4oaSIEV2ZW4gaGlnaGVyIGNvbmNlbnRyYXRpb24gIAozLiAqKkVhcmx5IFRocmVzaG9sZDoqKiBPbmx5IDIuMDclIG9mIHVzZXJzIGFjY291bnQgZm9yIDEwJSBvZiByZXNwb25zZXMKCiMjIyAqKlN0cmF0ZWdpYyBJbXBsaWNhdGlvbnM6KioKLSAqKlN1cnZleSBiaWFzIHJpc2s6KiogU21hbGwgdXNlciBzdWJzZXQgZHJpdmVzIG1ham9yaXR5IG9mIGZlZWRiYWNrCi0gKipFbmdhZ2VtZW50IG9wcG9ydHVuaXR5OioqIExhcmdlIHBvcnRpb24gb2YgdXNlcnMgdW5kZXJ1dGlsaXplZCAgCi0gKipRdWFsaXR5IGZvY3VzOioqIEhlYXZ5IHVzZXJzIHJlcXVpcmUgc3BlY2lhbCBhdHRlbnRpb24gZm9yIGRhdGEgcXVhbGl0eQotICoqR3Jvd3RoIHBvdGVudGlhbDoqKiBSZWNlbnQgdXNlcnMgc2hvdyBoaWdoZXIgZW5nYWdlbWVudCByYXRlcwoKIyMjICoqVGVjaG5pY2FsIFZhbGlkYXRpb246KioKLSAqKkZvcm11bGEgYWNjdXJhY3k6KiogYG1pbih3aGljaCgpKWAgY29ycmVjdGx5IGlkZW50aWZpZXMgdGhyZXNob2xkIGNyb3NzaW5ncwotICoqRGF0YSBpbnRlZ3JpdHk6KiogUHJvcGVyIGhhbmRsaW5nIG9mIG1pc3NpbmcgSURzIGVuc3VyZXMgdmFsaWQgdXNlciBhdHRyaWJ1dGlvbgotICoqVmlzdWFsaXphdGlvbiBjbGFyaXR5OioqIExvcmVueiBjdXJ2ZSBlZmZlY3RpdmVseSBzaG93cyBjb25jZW50cmF0aW9uIHBhdHRlcm5zCgojIyBGaWxlIE91dHB1dHMgR2VuZXJhdGVkCgoqKk91dHB1dCBTdHJ1Y3R1cmU6KioKYGBgCnRhc2syL291dHB1dHMvCuKUnOKUgOKUgCBhbGxfdXNlcnNfYW5hbHlzaXMuY3N2ICAgICAgICAgIyBEZXRhaWxlZCB1c2VyLWxldmVsIGFuYWx5c2lzCuKUnOKUgOKUgCByZWNlbnRfdXNlcnNfYW5hbHlzaXMuY3N2ICAgICAgIyA5MC1kYXkgY29ob3J0IGFuYWx5c2lzICAK4pSc4pSA4pSAIGRlbnNpdHlfdGhyZXNob2xkc19zdW1tYXJ5LmNzdiAjIE11bHRpcGxlIHRocmVzaG9sZCByZXN1bHRzCuKUnOKUgOKUgCBhbGxfdnNfcmVjZW50X2NvbXBhcmlzb24uY3N2ICAgIyBDb21wYXJhdGl2ZSBhbmFseXNpcwrilJzilIDilIAgbG9yZW56X2N1cnZlX2FsbF91c2Vycy5wbmcgICAgICMgTG9yZW56IGN1cnZlIHZpc3VhbGl6YXRpb24K4pSU4pSA4pSAIHVzZXJfY29tcGFyaXNvbi5wbmcgICAgICAgICAgICAjIEFsbCB2cyByZWNlbnQgY29tcGFyaXNvbiBwbG90CmBgYAoKLS0tCgojIPCfkrsgQ29kZSBJbXBsZW1lbnRhdGlvbgoKQmVsb3cgaXMgdGhlIGNvbXBsZXRlIHRlY2huaWNhbCBpbXBsZW1lbnRhdGlvbiBvZiB0aGUgYW5hbHlzaXMgZGVzY3JpYmVkIGFib3ZlLgoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UpCgojIExvYWQgbGlicmFyaWVzCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShyZWFkcikKbGlicmFyeShwbG90bHkpCmxpYnJhcnkoc2NhbGVzKQoKIyBEZWZpbmUgcmVsYXRpdmUgcGF0aHMgZnJvbSB0YXNrMiBmb2xkZXIKZGF0YV9kaXIgPC0gIi4uLy4uL2RhdGEiICAjIEdvIHVwIHR3byBsZXZlbHMgdG8gcmVhY2ggcHJvamVjdCByb290LCB0aGVuIGludG8gZGF0YQpvdXRwdXRfZGlyIDwtICIuL291dHB1dHMiICMgQ3JlYXRlIG91dHB1dHMgZm9sZGVyIHdpdGhpbiB0YXNrMgoKIyBDcmVhdGUgb3V0cHV0IGZvbGRlciBpZiBpdCBkb2Vzbid0IGV4aXN0CmlmICghZGlyLmV4aXN0cyhvdXRwdXRfZGlyKSkgewogIGRpci5jcmVhdGUob3V0cHV0X2RpciwgcmVjdXJzaXZlID0gVFJVRSkKfQoKIyBMb2FkIGRhdGFzZXRzCmZ1bGxfZGIgPC0gcmVhZFJEUyhmaWxlLnBhdGgoZGF0YV9kaXIsICJmdWxsLXJlc3BvbnNlLWRiLnJkcyIpKQp1c2VycyA8LSByZWFkUkRTKGZpbGUucGF0aChkYXRhX2RpciwgInVzZXJzLnJkcyIpKQpgYGAKCmBgYHtyIGRhdGEtY2xlYW5pbmd9CmNhdCgiPT09IERBVEEgQ0xFQU5JTkcgJiBQUkVQQVJBVElPTiA9PT1cbiIpCgojIENoZWNrIG1pc3NpbmcgdmFsdWVzCmNhdCgiTWlzc2luZyBJRHMgaW4gcmVzcG9uc2UgZGF0YWJhc2U6Iiwgc3VtKGlzLm5hKGZ1bGxfZGIkSUQpKSwgInJvd3NcbiIpCmNhdCgiVGhlc2UgbXVzdCBiZSByZW1vdmVkIGJlY2F1c2Ugd2UgY2Fubm90IGF0dHJpYnV0ZSByZXNwb25zZXMgdG8gdXNlcnMgd2l0aG91dCBJRHNcblxuIikKCiMgUmVtb3ZlIHJvd3Mgd2l0aCBtaXNzaW5nIHVzZXIgSUQgKGNhbm5vdCBiZSBhdHRyaWJ1dGVkIHRvIGFueSB1c2VyKQpmdWxsX2RiX2NsZWFuIDwtIGZ1bGxfZGIgJT4lCiAgZmlsdGVyKCFpcy5uYShJRCkpCgpjYXQoIkFmdGVyIGNsZWFuaW5nIC0gdXNhYmxlIHJlc3BvbnNlczoiLCBucm93KGZ1bGxfZGJfY2xlYW4pLCAiXG4iKQpgYGAKCmBgYHtyIHRhc2syLTEtaW1wbGVtZW50YXRpb259CmNhdCgiPT09IFRBU0sgMi4xOiBBTEwgVVNFUlMgQU5BTFlTSVMgPT09XG4iKQoKIyBTdGVwIDE6IENvdW50IHJlc3BvbnNlcyBwZXIgdXNlcgp1c2VyX2NvdW50cyA8LSBmdWxsX2RiX2NsZWFuICU+JQogIGNvdW50KElELCBuYW1lID0gInJlc3BvbnNlX2NvdW50IikKCiMgU3RlcCAyOiBKb2luIHdpdGggdXNlciBkYXRhIGFuZCBjYWxjdWxhdGUgY3VtdWxhdGl2ZSBzdGF0aXN0aWNzCnVzZXJfZGF0YSA8LSB1c2VyX2NvdW50cyAlPiUKICBsZWZ0X2pvaW4odXNlcnMsIGJ5ID0gIklEIikgJT4lCiAgYXJyYW5nZShkZXNjKHJlc3BvbnNlX2NvdW50KSkgJT4lICMgU29ydCBieSBoaWdoZXN0IHJlc3BvbmRlcnMgZmlyc3QKICBtdXRhdGUoCiAgICAjIENhbGN1bGF0ZSBydW5uaW5nIHRvdGFscwogICAgY3VtdWxhdGl2ZV9yZXNwb25zZXMgPSBjdW1zdW0ocmVzcG9uc2VfY291bnQpLAogICAgdG90YWxfcmVzcG9uc2VzID0gc3VtKHJlc3BvbnNlX2NvdW50KSwKICAgIGN1bXVsYXRpdmVfcGN0ID0gY3VtdWxhdGl2ZV9yZXNwb25zZXMgLyB0b3RhbF9yZXNwb25zZXMsCiAgICB1c2VyX3JhbmsgPSByb3dfbnVtYmVyKCksCiAgICB1c2VyX3BjdCA9IHVzZXJfcmFuayAvIG4oKSAjIFBlcmNlbnRhZ2Ugb2YgdXNlcnMgcmVwcmVzZW50ZWQKICApCgojIFN0ZXAgMzogRmluZCBtaW5pbXVtIHVzZXJzIGZvciA1MCUgdGhyZXNob2xkIHVzaW5nIG1pbih3aGljaCgpKQpjdXRvZmZfcm93IDwtIG1pbih3aGljaCh1c2VyX2RhdGEkY3VtdWxhdGl2ZV9wY3QgPj0gMC41KSkKcGN0XzUwX2FsbCA8LSB1c2VyX2RhdGEkdXNlcl9wY3RbY3V0b2ZmX3Jvd10gKiAxMDAKCmNhdChzcHJpbnRmKCJBTlNXRVI6ICUuMmYlJSBvZiB1c2VycyBhY2NvdW50IGZvciA1MCUlIG9mIGFsbCByZXNwb25zZXNcbiIsIHBjdF81MF9hbGwpKQoKIyBTYXZlIGRldGFpbGVkIGFuYWx5c2lzCndyaXRlX2Nzdih1c2VyX2RhdGEsIGZpbGUucGF0aChvdXRwdXRfZGlyLCAiYWxsX3VzZXJzX2FuYWx5c2lzLmNzdiIpKQpgYGAKCmBgYHtyIHRhc2syLTItaW1wbGVtZW50YXRpb259CmNhdCgiPT09IFRBU0sgMi4yOiBSRUNFTlQgVVNFUlMgKDkwIERBWVMpID09PVxuIikKCiMgU3RlcCAxOiBQYXJzZSBzaWdudXAgZGF0ZXMgYW5kIGRlZmluZSA5MC1kYXkgd2luZG93CnVzZXJzJHNpZ251cF9kYXRlIDwtIGFzLkRhdGUodXNlcnMkc2lnbnVwX2RhdGUsIGZvcm1hdCA9ICIlbS8lZC8lWSIpCmN1dG9mZl9kYXRlIDwtIG1heCh1c2VycyRzaWdudXBfZGF0ZSwgbmEucm0gPSBUUlVFKSAtIDkwCmNhdCgiOTAtZGF5IGN1dG9mZiBkYXRlOiIsIGFzLmNoYXJhY3RlcihjdXRvZmZfZGF0ZSksICJcbiIpCgojIFN0ZXAgMjogRmlsdGVyIHRvIHJlY2VudCB1c2VycyBvbmx5CnJlY2VudF91c2VycyA8LSB1c2VycyAlPiUKICBmaWx0ZXIoc2lnbnVwX2RhdGUgPj0gY3V0b2ZmX2RhdGUpCmNhdCgiUmVjZW50IHVzZXJzIGZvdW5kOiIsIG5yb3cocmVjZW50X3VzZXJzKSwgIlxuIikKCiMgU3RlcCAzOiBTdWJzZXQgcmVzcG9uc2UgZGF0YSBmb3IgcmVjZW50IHVzZXJzCnJlY2VudF9kYXRhIDwtIHVzZXJfZGF0YSAlPiUKICBmaWx0ZXIoSUQgJWluJSByZWNlbnRfdXNlcnMkSUQpICU+JQogIGFycmFuZ2UoZGVzYyhyZXNwb25zZV9jb3VudCkpICU+JQogIG11dGF0ZSgKICAgICMgUmVjYWxjdWxhdGUgY3VtdWxhdGl2ZSBzdGF0cyBmb3IgcmVjZW50IGNvaG9ydCBvbmx5CiAgICBjdW11bGF0aXZlX3Jlc3BvbnNlcyA9IGN1bXN1bShyZXNwb25zZV9jb3VudCksCiAgICB0b3RhbF9yZXNwb25zZXMgPSBzdW0ocmVzcG9uc2VfY291bnQpLAogICAgY3VtdWxhdGl2ZV9wY3QgPSBjdW11bGF0aXZlX3Jlc3BvbnNlcyAvIHRvdGFsX3Jlc3BvbnNlcywKICAgIHVzZXJfcmFuayA9IHJvd19udW1iZXIoKSwKICAgIHVzZXJfcGN0ID0gdXNlcl9yYW5rIC8gbigpCiAgKQoKIyBTdGVwIDQ6IEZpbmQgNTAlIHRocmVzaG9sZCBmb3IgcmVjZW50IHVzZXJzCmN1dG9mZl9yb3dfcmVjZW50IDwtIG1pbih3aGljaChyZWNlbnRfZGF0YSRjdW11bGF0aXZlX3BjdCA+PSAwLjUpKQpwY3RfNTBfcmVjZW50IDwtIHJlY2VudF9kYXRhJHVzZXJfcGN0W2N1dG9mZl9yb3dfcmVjZW50XSAqIDEwMAoKY2F0KHNwcmludGYoIkFOU1dFUjogJS4yZiUlIG9mIHJlY2VudCB1c2VycyBhY2NvdW50IGZvciA1MCUlIG9mIHRoZWlyIHJlc3BvbnNlc1xuIiwgcGN0XzUwX3JlY2VudCkpCgojIFNhdmUgcmVjZW50IHVzZXJzIGFuYWx5c2lzCndyaXRlX2NzdihyZWNlbnRfZGF0YSwgZmlsZS5wYXRoKG91dHB1dF9kaXIsICJyZWNlbnRfdXNlcnNfYW5hbHlzaXMuY3N2IikpCgojIENvbXBhcmlzb24gaW5zaWdodApjYXQoIlxuQ09NUEFSSVNPTjpcbiIpCmNhdCgiQWxsIHVzZXJzOiIsIHJvdW5kKHBjdF81MF9hbGwsIDIpLCAiJVxuIikKY2F0KCJSZWNlbnQgdXNlcnM6Iiwgcm91bmQocGN0XzUwX3JlY2VudCwgMiksICIlXG4iKQppZihwY3RfNTBfcmVjZW50IDwgcGN0XzUwX2FsbCkgewogIGNhdCgiUmVjZW50IHVzZXJzIHNob3cgSElHSEVSIGNvbmNlbnRyYXRpb24gKG1vcmUgYWN0aXZlIGhlYXZ5IHVzZXJzKVxuIikKfSBlbHNlIHsKICBjYXQoIlJlY2VudCB1c2VycyBzaG93IExPV0VSIGNvbmNlbnRyYXRpb24gKG1vcmUgZGlzdHJpYnV0ZWQgZW5nYWdlbWVudClcbiIpCn0KYGBgCgpgYGB7ciB0YXNrMi0zLWltcGxlbWVudGF0aW9ufQpjYXQoIj09PSBUQVNLIDIuMzogTVVMVElQTEUgVEhSRVNIT0xEIEFOQUxZU0lTID09PVxuIikKCiMgQ2FsY3VsYXRlIHVzZXIgcGVyY2VudGFnZXMgbmVlZGVkIGZvciB2YXJpb3VzIHJlc3BvbnNlIHRocmVzaG9sZHMKcmVzcG9uc2VfdGhyZXNob2xkcyA8LSBjKDAuMTAsIDAuMjAsIDAuMzAsIDAuNDAsIDAuNTAsIDAuNjAsIDAuNzAsIDAuODAsIDAuOTApCgpkZW5zaXR5X3N1bW1hcnkgPC0gc2FwcGx5KHJlc3BvbnNlX3RocmVzaG9sZHMsIGZ1bmN0aW9uKHRocmVzaCkgewogIGN1dG9mZl9pbmRleCA8LSBtaW4od2hpY2godXNlcl9kYXRhJGN1bXVsYXRpdmVfcGN0ID49IHRocmVzaCkpCiAgdXNlcl9kYXRhJHVzZXJfcGN0W2N1dG9mZl9pbmRleF0gKiAxMDAKfSkKCm5hbWVzKGRlbnNpdHlfc3VtbWFyeSkgPC0gcGFzdGUwKHJlc3BvbnNlX3RocmVzaG9sZHMgKiAxMDAsICIlIikKCmNhdCgiVXNlciBwZXJjZW50YWdlIG5lZWRlZCBmb3IgZWFjaCByZXNwb25zZSB0aHJlc2hvbGQ6XG4iKQpmb3IgKGkgaW4gMTpsZW5ndGgoZGVuc2l0eV9zdW1tYXJ5KSkgewogIGNhdChzcHJpbnRmKCLigKIgJS4yZiUlIG9mIHVzZXJzIOKGkiAlcyBvZiByZXNwb25zZXNcbiIsIAogICAgICAgICAgICAgIGRlbnNpdHlfc3VtbWFyeVtpXSwgbmFtZXMoZGVuc2l0eV9zdW1tYXJ5KVtpXSkpCn0KCiMgQ3JlYXRlIHN1bW1hcnkgdGFibGUKc3VtbWFyeV90YWJsZSA8LSBkYXRhLmZyYW1lKAogIFJlc3BvbnNlX1RocmVzaG9sZCA9IG5hbWVzKGRlbnNpdHlfc3VtbWFyeSksCiAgVXNlcl9QZXJjZW50YWdlX05lZWRlZCA9IHJvdW5kKGRlbnNpdHlfc3VtbWFyeSwgMikKKQoKd3JpdGVfY3N2KHN1bW1hcnlfdGFibGUsIGZpbGUucGF0aChvdXRwdXRfZGlyLCAiZGVuc2l0eV90aHJlc2hvbGRzX3N1bW1hcnkuY3N2IikpCmBgYAoKYGBge3IgbG9yZW56LWN1cnZlLXZpc3VhbGl6YXRpb259CmNhdCgiPT09IExPUkVOWiBDVVJWRSBWSVNVQUxJWkFUSU9OID09PVxuIikKCiMgQ3JlYXRlIExvcmVueiBjdXJ2ZSBwbG90CmxvcmVuel9wbG90IDwtIGdncGxvdCh1c2VyX2RhdGEsIGFlcyh4ID0gdXNlcl9wY3QsIHkgPSBjdW11bGF0aXZlX3BjdCkpICsKICBnZW9tX2xpbmUoY29sb3IgPSAiZGFya2JsdWUiLCBzaXplID0gMS4yLCBhbHBoYSA9IDAuOCkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgCiAgICAgICAgICAgICAgY29sb3IgPSAiZ3JheTUwIiwgYWxwaGEgPSAwLjcpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAwLjcsIHkgPSAwLjMsIAogICAgICAgICAgIGxhYmVsID0gIlBlcmZlY3QgRXF1YWxpdHlcbig0NcKwIGxpbmUpIiwgCiAgICAgICAgICAgY29sb3IgPSAiZ3JheTUwIiwgc2l6ZSA9IDMpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAwLjMsIHkgPSAwLjcsIAogICAgICAgICAgIGxhYmVsID0gIkFjdHVhbCBEaXN0cmlidXRpb25cbihIaWdoIENvbmNlbnRyYXRpb24pIiwgCiAgICAgICAgICAgY29sb3IgPSAiZGFya2JsdWUiLCBzaXplID0gMykgKwogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQsIG5hbWUgPSAiQ3VtdWxhdGl2ZSAlIG9mIFVzZXJzIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQsIG5hbWUgPSAiQ3VtdWxhdGl2ZSAlIG9mIFJlc3BvbnNlcyIpICsKICBsYWJzKHRpdGxlID0gIlJlc3BvbnNlIERlbnNpdHk6IExvcmVueiBDdXJ2ZSBBbmFseXNpcyIsCiAgICAgICBzdWJ0aXRsZSA9IHBhc3RlMCgiRGlzdGFuY2UgZnJvbSBkaWFnb25hbCBzaG93cyBjb25jZW50cmF0aW9uIGxldmVsIHwgIiwKICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQocGN0XzUwX2FsbCwgMSksICIlIG9mIHVzZXJzIOKGkiA1MCUgb2YgcmVzcG9uc2VzIikpICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEyKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQsIGZhY2UgPSAiYm9sZCIpKQoKIyBEaXNwbGF5IGludGVyYWN0aXZlIHZlcnNpb24KaW50ZXJhY3RpdmVfbG9yZW56IDwtIGdncGxvdGx5KGxvcmVuel9wbG90LCB0b29sdGlwID0gYygieCIsICJ5IikpCmludGVyYWN0aXZlX2xvcmVuegoKIyBTYXZlIHN0YXRpYyB2ZXJzaW9uCmdnc2F2ZShmaWxlLnBhdGgob3V0cHV0X2RpciwgImxvcmVuel9jdXJ2ZV9hbGxfdXNlcnMucG5nIiksIGxvcmVuel9wbG90LCAKICAgICAgIHdpZHRoID0gMTAsIGhlaWdodCA9IDYsIGRwaSA9IDMwMCkKCmNhdCgiTG9yZW56IEN1cnZlIEludGVycHJldGF0aW9uOlxuIikKY2F0KCLigKIgRGlhZ29uYWwgbGluZSA9IFBlcmZlY3QgZXF1YWxpdHkgKGV2ZXJ5b25lIGNvbnRyaWJ1dGVzIGVxdWFsbHkpXG4iKQpjYXQoIuKAoiBDdXJ2ZWQgbGluZSA9IEFjdHVhbCBkaXN0cmlidXRpb24gKGNvbmNlbnRyYXRlZCBhbW9uZyBmZXcgdXNlcnMpXG4iKQpjYXQoIuKAoiBMYXJnZXIgYXJlYSBiZXR3ZWVuIGxpbmVzID0gSGlnaGVyIGNvbmNlbnRyYXRpb25cbiIpCmBgYAoKYGBge3IgY29tcGFyaXNvbi1hbGwtdnMtcmVjZW50fQpjYXQoIj09PSBDT01QQVJJU09OOiBBTEwgdnMgUkVDRU5UIFVTRVJTID09PVxuIikKCiMgQ2FsY3VsYXRlIHRocmVzaG9sZHMgZm9yIHJlY2VudCB1c2VycwpyZWNlbnRfc3VtbWFyeSA8LSBzYXBwbHkocmVzcG9uc2VfdGhyZXNob2xkcywgZnVuY3Rpb24odGhyZXNoKSB7CiAgaWYgKG1heChyZWNlbnRfZGF0YSRjdW11bGF0aXZlX3BjdCkgPj0gdGhyZXNoKSB7CiAgICBjdXRvZmZfaW5kZXggPC0gbWluKHdoaWNoKHJlY2VudF9kYXRhJGN1bXVsYXRpdmVfcGN0ID49IHRocmVzaCkpCiAgICByZWNlbnRfZGF0YSR1c2VyX3BjdFtjdXRvZmZfaW5kZXhdICogMTAwCiAgfSBlbHNlIHsKICAgIE5BCiAgfQp9KQoKIyBDcmVhdGUgY29tcGFyaXNvbiBkYXRhc2V0CmNvbXBhcmlzb25fZGF0YSA8LSBkYXRhLmZyYW1lKAogIFJlc3BvbnNlX1RocmVzaG9sZCA9IHJlc3BvbnNlX3RocmVzaG9sZHMgKiAxMDAsCiAgQWxsX1VzZXJzID0gZGVuc2l0eV9zdW1tYXJ5LAogIFJlY2VudF9Vc2VycyA9IHJlY2VudF9zdW1tYXJ5CikgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKEFsbF9Vc2VycywgUmVjZW50X1VzZXJzKSwgCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIlVzZXJfR3JvdXAiLCAKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gIlVzZXJfUGVyY2VudGFnZSIpCgojIENyZWF0ZSBjb21wYXJpc29uIHBsb3QKY29tcGFyaXNvbl9wbG90IDwtIGdncGxvdChjb21wYXJpc29uX2RhdGEsIGFlcyh4ID0gUmVzcG9uc2VfVGhyZXNob2xkLCB5ID0gVXNlcl9QZXJjZW50YWdlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gVXNlcl9Hcm91cCwgbGluZXR5cGUgPSBVc2VyX0dyb3VwKSkgKwogIGdlb21fbGluZShzaXplID0gMS4yKSArCiAgZ2VvbV9wb2ludChzaXplID0gMykgKwogIHNjYWxlX3hfY29udGludW91cyhuYW1lID0gIlJlc3BvbnNlIFRocmVzaG9sZCAoJSkiLCBicmVha3MgPSBzZXEoMTAsIDkwLCAxMCkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZSA9ICJVc2VyIFBlcmNlbnRhZ2UgTmVlZGVkICglKSIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiQWxsX1VzZXJzIiA9ICJkYXJrYmx1ZSIsICJSZWNlbnRfVXNlcnMiID0gImRhcmtyZWQiKSkgKwogIGxhYnModGl0bGUgPSAiUmVzcG9uc2UgQ29uY2VudHJhdGlvbjogQWxsIFVzZXJzIHZzIFJlY2VudCBVc2VycyAoOTAgRGF5cykiLAogICAgICAgc3VidGl0bGUgPSAiTG93ZXIgdmFsdWVzIGluZGljYXRlIGhpZ2hlciBjb25jZW50cmF0aW9uIGFtb25nIGZld2VyIHVzZXJzIiwKICAgICAgIGNvbG9yID0gIlVzZXIgR3JvdXAiLCBsaW5ldHlwZSA9ICJVc2VyIEdyb3VwIikgKwogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTIpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCgpwcmludChjb21wYXJpc29uX3Bsb3QpCmdnc2F2ZShmaWxlLnBhdGgob3V0cHV0X2RpciwgInVzZXJfY29tcGFyaXNvbi5wbmciKSwgY29tcGFyaXNvbl9wbG90LCAKICAgICAgIHdpZHRoID0gMTAsIGhlaWdodCA9IDYsIGRwaSA9IDMwMCkKCiMgU2F2ZSBjb21wYXJpc29uIGRhdGEKd3JpdGVfY3N2KGNvbXBhcmlzb25fZGF0YSwgZmlsZS5wYXRoKG91dHB1dF9kaXIsICJhbGxfdnNfcmVjZW50X2NvbXBhcmlzb24uY3N2IikpCmBgYAoKYGBge3IgZmluYWwtc3VtbWFyeX0KY2F0KCI9PT0gQU5BTFlTSVMgQ09NUExFVEUgPT09XG4iKQpjYXQoIkdlbmVyYXRlZCBmaWxlcyBpbiIsIG5vcm1hbGl6ZVBhdGgob3V0cHV0X2RpciksICI6XG4iKQpvdXRwdXRfZmlsZXMgPC0gbGlzdC5maWxlcyhvdXRwdXRfZGlyKQpmb3IgKGZpbGUgaW4gb3V0cHV0X2ZpbGVzKSB7CiAgY2F0KCLigKIiLCBmaWxlLCAiXG4iKQp9CgpjYXQoIlxuRmluYWwgQW5zd2VyczpcbiIpCmNhdCgiVGFzayAyLjE6Iiwgcm91bmQocGN0XzUwX2FsbCwgMiksICIlIG9mIGFsbCB1c2VycyDihpIgNTAlIG9mIHJlc3BvbnNlc1xuIikKY2F0KCJUYXNrIDIuMjoiLCByb3VuZChwY3RfNTBfcmVjZW50LCAyKSwgIiUgb2YgcmVjZW50IHVzZXJzIOKGkiA1MCUgb2YgdGhlaXIgcmVzcG9uc2VzXG4iKQpjYXQoIlRhc2sgMi4zOiBMb3JlbnogY3VydmUgdmlzdWFsaXphdGlvbiB3aXRoIG11bHRpcGxlIHRocmVzaG9sZCBhbmFseXNpc1xuIikKYGBgIA==